package com.examlpe.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.examlpe.lotterysystem.common.exception.ServiceException;
import com.examlpe.lotterysystem.common.utils.JacksonUtil;
import com.examlpe.lotterysystem.common.utils.MailUtil;
import com.examlpe.lotterysystem.common.utils.SMSUtil;
import com.examlpe.lotterysystem.controller.param.DrawPrizeParam;
import com.examlpe.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.examlpe.lotterysystem.dao.dataobject.WinningRecordDO;
import com.examlpe.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.examlpe.lotterysystem.dao.mapper.WinningRecordMapper;
import com.examlpe.lotterysystem.service.DrawPrizeService;
import com.examlpe.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.examlpe.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.examlpe.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.examlpe.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.examlpe.lotterysystem.service.enums.ActivityStatusEnum;
import com.examlpe.lotterysystem.service.enums.ActivityUserStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.examlpe.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@Slf4j
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private SMSUtil smsUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @RabbitHandler
    public void process(Map<String, String> message) throws Exception {
        //成功接收队列中的消息
        log.info("mq成功接收到消息，message：{}",
                JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        //处理抽奖流程
        try {
            //校验抽奖请求是否通过
            //发起两个一样的抽奖
            if(!drawPrizeService.checkDrawPrizeParam(param)){
                return;
            }
            //活动/奖品/人员状态扭转(设计模式)
            statusConvert(param);
            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);
            //通知中奖者(邮箱，短信)
            //抽奖后续流程 异步，并发处理后续流程
            syncExcute(winningRecordDOList);
        } catch (ServiceException e) {
            log.error("处理MQ消息异常,{}:{}", e.getCode(), e.getMassage(), e);
            //保持事务一致性(回滚
            rollback(param);
            //抛出异常：消息重试（解决异常：代码bug、网络问题、服务问题）
            throw e;
        } catch (Exception e) {
            log.error("出现异常{}", e);
            //保持事务一致性
            rollback(param);
            throw e;
        }

    }

    /*处理抽奖回滚行为*/
    //恢复处理请求之前的状态
    private void rollback(DrawPrizeParam param) {

        //1.回滚状态
        //是否需要回滚
        if (!statusNeedRollback(param)) {
            //不需要回滚
            return;
        }
        //需要回滚
        rollbackStatus(param);

        //2.回滚中奖者名单
        if (!winnerNeeedRollback(param)) {
            //不需要回滚
            return;
        }
        //需要回滚
        rollbackWinner(param);
    }

    /**
     * 回滚中奖记录,删除奖品下的中奖者
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());
    }

    private boolean winnerNeeedRollback(DrawPrizeParam param) {
        //判断活动中的奖品是否存在中奖者
        int count =  winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count>0;
    }

    /*恢复相关状态*/
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPriceId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPriceStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIDs(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner :: getUserId).collect(Collectors.toList()));
        convertActivityStatusDTO.setTargeUSerStatus(ActivityUserStatusEnum.INIT);



        // 涉及状态的恢复工作 使用ActivityStatusManager
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);

    }
    /*判断是否需要回滚*/
    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断活动+奖品+关联人员相关状态是否扭转
        //状态转换时，保持了事务一致性
        //只需要判断人员或奖品是否扭转过
        //奖品状态是否扭转就能判断出全部状态是否扭转
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectADId(param.getActivityId(), param.getPrizeId());
        return activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());

    }

    //并发处理抽奖后续流程
    private void syncExcute(List<WinningRecordDO> winningRecordDOList) {
        ///通过线程池处理异步任务
        //短信通知
        threadPoolTaskExecutor.execute(() -> sendMessage(winningRecordDOList));
        //邮件通知
        threadPoolTaskExecutor.execute(() -> sendMail(winningRecordDOList));
    }

    /*发送邮件*/
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空，不需要发邮件");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            String subject = "中奖通知";
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "："
                    + winningRecordDO.getPrizeName()
                    + "。获奖时间为" + DateUtil.formatTime(winningRecordDO.getWinningTime())
                    + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(), subject, context);
        }
    }

    /*发送短信*/
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空，不需要发短信");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            String templateCode = "SMS_479250167";
            String phoneNumber = winningRecordDO.getWinnerPhoneNumber().getValue();
            Map<String, String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));
            smsUtil.sendMessage(templateCode,
                    phoneNumber,
                    JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 状态扭转  加入设计模式（责任链，策略模式）
     *
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPriceId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPriceStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIDs(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargeUSerStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }

}
